package com.jeesite.modules.digitalize.util.word.compare;

import cn.hutool.core.collection.CollectionUtil;
import com.google.common.collect.Lists;
import com.jeesite.common.io.ResourceUtils;
import com.jeesite.common.lang.DateUtils;
import com.jeesite.common.utils.word.BookMark;
import com.jeesite.common.utils.word.BookMarks;
import com.jeesite.modules.digitalize.constants.CompareEnum;
import com.jeesite.modules.digitalize.entity.DigitalizeComponentCompare;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlCursor;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.w3c.dom.Node;

import javax.servlet.ServletOutputStream;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import java.io.*;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class WordExportUtils  {
    /** 内部使用的文档对象 **/
    private XWPFDocument document;

    private BookMarks bookMarks = null;

    /**
     * 为文档设置模板
     * @param templatePath 模板文件名称
     */
    public void setTemplate(String templatePath) {
        try {
            if (StringUtils.startsWith(templatePath, "classpath:")) {
                InputStream is = ResourceUtils.getResourceFileStream(templatePath);
                document = new XWPFDocument(OPCPackage.open(is));
            }else {
                document = new XWPFDocument(OPCPackage.open(templatePath));
            }
            bookMarks = new BookMarks(document);
        } catch (IOException | InvalidFormatException e) {
            e.printStackTrace();
        }
    }

    /**
     * 进行标签替换的例子,传入的Map中，key表示标签名称，value是替换的信息
     * @param indicator
     */
    public void replaceBookMark(Map<String, String> indicator) {
        //循环进行替换
        Iterator<String> bookMarkIter = bookMarks.getNameIterator();
        while (bookMarkIter.hasNext()) {
            String bookMarkName = bookMarkIter.next();

            //得到标签名称
            BookMark bookMark = bookMarks.getBookmark(bookMarkName);

            //进行替换
            if (indicator.get(bookMarkName) != null) {
                bookMark.insertTextAtBookMark(indicator.get(bookMarkName), BookMark.INSERT_BEFORE);
            }

        }

    }

    public void fillTableAtBookMark(String bookMarkName, List<Map<String, String>> content) {

        //rowNum来比较标签在表格的哪一行
        int rowNum = 0;

        //首先得到标签
        BookMark bookMark = bookMarks.getBookmark(bookMarkName);
        Map<String, String> columnMap = new HashMap<String, String>();
        Map<String, Node> styleNode = new HashMap<String, Node>();

        //标签是否处于表格内
        if (bookMark.isInTable()) {

            //获得标签对应的Table对象和Row对象
            XWPFTable table = bookMark.getContainerTable();
            XWPFTableRow row = bookMark.getContainerTableRow();
//			CTRow ctRow = row.getCtRow();
            List<XWPFTableCell> rowCell = row.getTableCells();
            for (int i = 0; i < rowCell.size(); i++) {
                columnMap.put(i + "", rowCell.get(i).getText().trim());
                //System.out.println(rowCell.get(i).getParagraphs().get(0).createRun().getFontSize());
                //System.out.println(rowCell.get(i).getParagraphs().get(0).getCTP());
                //System.out.println(rowCell.get(i).getParagraphs().get(0).getStyle());

                //获取该单元格段落的xml，得到根节点
                Node node1 = rowCell.get(i).getParagraphs().get(0).getCTP().getDomNode();

                //遍历根节点的所有子节点
                for (int x = 0; x < node1.getChildNodes().getLength(); x++) {
                    if (node1.getChildNodes().item(x).getNodeName().equals(BookMark.RUN_NODE_NAME)) {
                        Node node2 = node1.getChildNodes().item(x);

                        //遍历所有节点为"w:r"的所有自己点，找到节点名为"w:rPr"的节点
                        for (int y = 0; y < node2.getChildNodes().getLength(); y++) {
                            if (node2.getChildNodes().item(y).getNodeName().endsWith(BookMark.STYLE_NODE_NAME)) {

                                //将节点为"w:rPr"的节点(字体格式)存到HashMap中
                                styleNode.put(i + "", node2.getChildNodes().item(y));
                            }
                        }
                    } else {
                        continue;
                    }
                }
            }

            //循环对比，找到该行所处的位置，删除改行
            for (int i = 0; i < table.getNumberOfRows(); i++) {
                if (table.getRow(i).equals(row)) {
                    rowNum = i;
                    break;
                }
            }
            table.removeRow(rowNum);

            for (int i = 0; i < content.size(); i++) {
                //创建新的一行,单元格数是表的第一行的单元格数,
                //后面添加数据时，要判断单元格数是否一致
                XWPFTableRow tableRow = table.createRow();
                CTTrPr trPr = tableRow.getCtRow().addNewTrPr();
                CTHeight ht = trPr.addNewTrHeight();
                ht.setVal(BigInteger.valueOf(360));
            }

            //得到表格行数
            int rcount = table.getNumberOfRows();
            for (int i = rowNum; i < rcount; i++) {
                XWPFTableRow newRow = table.getRow(i);

                //判断newRow的单元格数是不是该书签所在行的单元格数
                if (newRow.getTableCells().size() != rowCell.size()) {

                    //计算newRow和书签所在行单元格数差的绝对值
                    //如果newRow的单元格数多于书签所在行的单元格数，不能通过此方法来处理，可以通过表格中文本的替换来完成
                    //如果newRow的单元格数少于书签所在行的单元格数，要将少的单元格补上
                    int sub = Math.abs(newRow.getTableCells().size() - rowCell.size());
                    //将缺少的单元格补上
                    for (int j = 0; j < sub; j++) {
                        newRow.addNewTableCell();
                    }
                }

                List<XWPFTableCell> cells = newRow.getTableCells();

                for (int j = 0; j < cells.size(); j++) {
                    XWPFParagraph para = cells.get(j).getParagraphs().get(0);
                    XWPFRun run = para.createRun();
                    if (content.get(i - rowNum).get(columnMap.get(j + "")) != null) {

                        //改变单元格的值，标题栏不用改变单元格的值
                        run.setText(content.get(i - rowNum).get(columnMap.get(j + "")) + "");

                        //将单元格段落的字体格式设为原来单元格的字体格式
                        run.getCTR().getDomNode().insertBefore(styleNode.get(j + "").cloneNode(true), run.getCTR().getDomNode().getFirstChild());
                    }

                    para.setAlignment(ParagraphAlignment.CENTER);
                }
            }
        }
    }

    public void replaceText(Map<String, String> bookmarkMap, String bookMarkName) {

        //首先得到标签
        BookMark bookMark = bookMarks.getBookmark(bookMarkName);
        //获得书签标记的表格
        XWPFTable table = bookMark.getContainerTable();
        //获得所有的表
        //Iterator<XWPFTable> it = document.getTablesIterator();

        if (table != null) {
            //得到该表的所有行
            int rcount = table.getNumberOfRows();
            for (int i = 0; i < rcount; i++) {
                XWPFTableRow row = table.getRow(i);

                //获到改行的所有单元格
                List<XWPFTableCell> cells = row.getTableCells();
                for (XWPFTableCell c : cells) {
                    for (Map.Entry<String, String> e : bookmarkMap.entrySet()) {
                        if (c.getText().equals(e.getKey())) {

                            //删掉单元格内容
                            c.removeParagraph(0);

                            //给单元格赋值
                            c.setText(e.getValue());
                        }
                    }
                }
            }
        }
    }

    public void saveAs(String fileName) {
        File newFile = new File(fileName);
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(newFile);
            this.document.write(fos);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fos != null){
                    fos.flush();
                }
                if (fos != null){
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void createHeadingStyle(XWPFDocument doc, XWPFStyles styles, String strStyleId,
                                   int headingLevel, int pointSize, String hexColor, String typefaceName) {
        //创建样式
        CTStyle ctStyle = CTStyle.Factory.newInstance();
        //设置id
        ctStyle.setStyleId(strStyleId);

        CTString styleName = CTString.Factory.newInstance();
        styleName.setVal(strStyleId);
        ctStyle.setName(styleName);

        CTDecimalNumber indentNumber = CTDecimalNumber.Factory.newInstance();
        indentNumber.setVal(BigInteger.valueOf(headingLevel));

        // 数字越低在格式栏中越突出
        ctStyle.setUiPriority(indentNumber);

        CTOnOff onoffnull = CTOnOff.Factory.newInstance();
        ctStyle.setUnhideWhenUsed(onoffnull);

        // 样式将显示在“格式”栏中
        ctStyle.setQFormat(onoffnull);

        // 样式定义给定级别的标题
        CTPPr ppr = CTPPr.Factory.newInstance();
        ppr.setOutlineLvl(indentNumber);
        ctStyle.setPPr(ppr);

        XWPFStyle style = new XWPFStyle(ctStyle);

        CTHpsMeasure size = CTHpsMeasure.Factory.newInstance();
        size.setVal(new BigInteger(String.valueOf(pointSize)));
        CTHpsMeasure size2 = CTHpsMeasure.Factory.newInstance();
        size2.setVal(new BigInteger(String.valueOf(pointSize)));

        CTFonts fonts = CTFonts.Factory.newInstance();
        if(typefaceName == null || typefaceName.equals("")) typefaceName = "宋体";
        fonts.setAscii(typefaceName);	//字体

        CTRPr rpr = CTRPr.Factory.newInstance();
        //rpr.setRFonts(fonts);
        rpr.setSz(size);
        rpr.setSzCs(size2);	//字体大小

        CTColor color=CTColor.Factory.newInstance();
        color.setVal(hexToBytes(hexColor));
        rpr.setColor(color);	//字体颜色
        style.getCTStyle().setRPr(rpr);
        // is a null op if already defined

        style.setType(STStyleType.PARAGRAPH);
        styles.addStyle(style);
    }

    public static byte[] hexToBytes(String hexString) {
        HexBinaryAdapter adapter = new HexBinaryAdapter();
        byte[] bytes = adapter.unmarshal(hexString);
        return bytes;
    }
    public static void addLine(List<String> list, XWPFParagraph para) {
        for (String s : list) {
            XWPFRun run = para.createRun();
            run.setText(s);
            run.setFontSize(14);
            run.addBreak();
        }
    }
    public void export(DigitalizeComponentCompare com, ChangeData data, ServletOutputStream outputStream) throws  IOException {
        String fileName = "classpath:" + StringUtils.substringBeforeLast(StringUtils.replace(
                WordExportUtils.class.getName(), ".", "/"), "/") + "/模型对比分析报告.docx";
        this.setTemplate(fileName);

        String headStyle = "headStyle";
        XWPFStyles newStyles = document.createStyles();
        createHeadingStyle(document, newStyles, headStyle, 1, 30, "000000", "宋体");

        Map<String, String> content0 = new HashMap<String, String>();
        content0.put("name", com.getName());
        content0.put("createDate", DateUtils.formatDate(com.getCreateDate()));
        CompareEnum compareEnum = CompareEnum.getByCode(com.getType());
        content0.put("type", compareEnum.getDesc());
        if (CompareEnum.VERSION.equals(compareEnum)) {
            content0.put("componentName", String.format("%s", com.getOneComponentName()));
        } else {
            content0.put("componentName", String.format("%s & %s", com.getOneComponentName(), com.getTwoComponentName()));
        }
        content0.put("version", String.format("%s & %s", com.getOneVersion(), com.getTwoVersion()));
        this.replaceBookMark(content0);

        XWPFTable table = document.createTable(1, 1);
        XWPFTableCell cell = table.getRow(0).getCell(0);
        cell.getCTTc().addNewTcPr().addNewTcW().setW(BigInteger.valueOf(10000));
        XWPFParagraph para = cell.getParagraphs().get(0);

        addLine(Lists.newArrayList("经对比：", String.format("新增构件：%d", CollectionUtil.size(data.getNewDiffList())), String.format("删除构件：%d", CollectionUtil.size(data.getDeleteDiffList())), String.format("修改构件：%d",  CollectionUtil.size(data.getChangeDiffList()))), para);

        // 一、  模型基本信息
        XWPFParagraph para1 = document.createParagraph();
        para1.setStyle(headStyle);
        XWPFRun run1 = para1.createRun();
        run1.setText("一、模型基本信息");
        run1.setBold(true);
        modelTable(com);

        // 二、  对比结果
        XWPFParagraph para2 = document.createParagraph();
        para2.setStyle(headStyle);
        XWPFRun run12 = para2.createRun();
        run12.setText("二、对比结果");
        run12.setBold(true);

        AtomicInteger index = new AtomicInteger(1);
        diffTable(data.getNewDiffList(), DiffTypeEnum.NEW, index);
        diffTable(data.getDeleteDiffList(), DiffTypeEnum.DELETE, index);
        changeTable(data.getChangeDiffList(), index);
        document.write(outputStream);

    }


    public void modelTable(DigitalizeComponentCompare compare) {
        XWPFTable table = document.createTable();
        XWPFTableRow row = table.getRow(0);
        row.getCell(0).setText("模型名称");
        XWPFParagraph xwpfParagraph1 = row.getCell(0).addParagraph();
        xwpfParagraph1.createRun().setBold(true);
        row.createCell().setText("模型版本");
        row.createCell().setText("fileId");
        row.getTableCells().stream().forEach(i -> {
            i.setColor("E7E6E6");
        });

        XWPFTableRow row2 = table.createRow();
        row2.getCell(0).setText(compare.getOneComponentName());
        row2.getCell(1).setText(compare.getOneVersion());
        row2.getCell(2).setText(String.valueOf(compare.getOneComponentId()));


        XWPFTableRow row23 = table.createRow();
        row23.getCell(0).setText(compare.getTwoComponentName());
        row23.getCell(1).setText(compare.getTwoVersion());
        row23.getCell(2).setText(String.valueOf(compare.getTwoComponentId()));

        int[] COLUMN_WIDTHS2 = new int[]{2000, 2000, 2000, 6000, 5000, 5000};
        setTableGridCol2(table, COLUMN_WIDTHS2);
        XWPFRun run2 = document.createParagraph().insertNewRun(0);
        run2.addBreak();
        run2.addCarriageReturn();
    }

    public void diffTable(List<MyModelCompareDiff> list, DiffTypeEnum diffTypeEnum, AtomicInteger index) {
        if (list == null || list.size() == 0) {
            return;
        }
        XWPFRun run = document.createParagraph().insertNewRun(0);
        run.setText(String.format(diffTypeEnum.equals(DiffTypeEnum.NEW) ? "%d. 新增构件(%s)" : "%d. 删除构件(%s)", index.getAndIncrement(), list.size()));
        run.setBold(true);
        run.addBreak(BreakClear.ALL);
        Map<String, List<MyModelCompareDiff>> collect = list.stream().collect(Collectors.groupingBy(MyModelCompareDiff::getCategoryName));
        XWPFTable table = document.createTable();
        XWPFTableRow row = table.getRow(0);
        row.getCell(0).setText("构件类型");
        XWPFParagraph xwpfParagraph1 = row.getCell(0).addParagraph();
        xwpfParagraph1.createRun().setBold(true);
        row.createCell().setText("构件名称");
        row.createCell().setText("构件ID");
        row.getTableCells().stream().forEach(i -> {
            i.setColor("E7E6E6");
        });
        for (Map.Entry<String, List<MyModelCompareDiff>> e : collect.entrySet()) {
            String key = e.getKey();
            List<MyModelCompareDiff> valueList = e.getValue();
            for (MyModelCompareDiff diff : valueList) {
                XWPFTableRow row2 = table.createRow();
                row2.getCell(0).setText(key);
                row2.getCell(1).setText(diff.getElementName());
                row2.getCell(2).setText(diff.getElementId());
                key = "";
            }
        }
        int[] COLUMN_WIDTHS2 = new int[]{2000, 2000, 2000, 6000, 5000, 5000};
        setTableGridCol2(table, COLUMN_WIDTHS2);
        XWPFRun run2 = document.createParagraph().insertNewRun(0);
        run2.addBreak();
        run2.addCarriageReturn();
    }

    public void changeTable(List<ComChange> data,  AtomicInteger index) {
        if (CollectionUtil.isEmpty(data)) {
            return;
        }
        XWPFRun run = document.createParagraph().insertNewRun(0);
        run.setText(String.format("%d. 修改构件(%s)", index.getAndIncrement(), data.size()));
        run.setBold(true);
        run.addBreak(BreakClear.ALL);

        for (ComChange datum : data) {
            Map<String, List<ComChangeAttribute>> changedMap = datum.getChangedMap();
            XWPFTable table = document.createTable();
            XWPFTableRow row = table.getRow(0);
            row.getCell(0).setText("构件类型");
            XWPFParagraph xwpfParagraph1 = row.getCell(0).addParagraph();
            xwpfParagraph1.createRun().setBold(true);
            row.createCell().setText("构件名称");
            row.createCell().setText("");
            row.createCell().setText("构件ID");
            row.getTableCells().stream().forEach(i -> {
                i.setColor("E7E6E6");
            });
            int[] COLUMN_WIDTHS = new int[]{5000, 5000, 5000, 5000, 5000, 5000};

            XWPFTableRow row1 = table.createRow();
            row1.getCell(0).setText(datum.getCategoryName());
            row1.getCell(1).setText(datum.getElementName());
            row1.getCell(2).setText("修改前构件");
            row1.getCell(3).setText(datum.getElementId());
            XWPFTableRow row2 = table.createRow();
            row2.getCell(0).setText("");
            row2.getCell(1).setText(datum.getElementName());
            row2.getCell(2).setText("修改后构件");
            row2.getCell(3).setText(datum.getElementId());

            //setTableGridCol(table, COLUMN_WIDTHS);
            XWPFTableRow changeRemarkRow = table.createRow();
            mergeCellsByCol(table, changeRemarkRow, 0, 3);
            XWPFTableRow changeRow = table.createRow();
            mergeCellsByCol(table, changeRow, 0, 3);
            changeRemarkRow.getCell(0).setText("对比结果:" +datum.getElementName()+ "，有"+ datum.getSize() +"处属性被修改");

            XWPFTableCell innerCell = changeRow.getCell(0);
            XWPFParagraph xwpfParagraph = innerCell.addParagraph();
            XmlCursor cursor = xwpfParagraph.getCTP().newCursor();
            XWPFTable iTable = innerCell.insertNewTbl(cursor);

            XWPFTableRow iHeader = iTable.createRow();
            iHeader.createCell().setText("变更属性");
            iHeader.createCell().setText("修改前");
            iHeader.createCell().setText("修改后");


            for (Map.Entry<String, List<ComChangeAttribute>> entry : changedMap.entrySet()) {
                String key = entry.getKey();
                List<ComChangeAttribute> value = entry.getValue();
                XWPFTableRow rowKey = iTable.createRow();
                mergeCellsByCol(iTable, rowKey,0, 2);
                rowKey.getCell(0).setText(key);

                for (ComChangeAttribute comChangeAttribute : value) {
                    XWPFTableRow row3 = iTable.createRow();
                    row3.getCell(0).setText(comChangeAttribute.getKey());
                    row3.getCell(1).setText(comChangeAttribute.getValue());
                    row3.getCell(2).setText(comChangeAttribute.getNewValue());
                }
            }
            int[] COLUMN_WIDTHS2 = new int[]{3000, 3000, 3000, 6000, 5000, 5000};
            setTableGridCol(iTable, COLUMN_WIDTHS2);
            XWPFRun run2 = document.createParagraph().insertNewRun(0);
            run2.addBreak();
            run2.addCarriageReturn();
        }
    }

    private static void setTableGridCol(XWPFTable table, int[] columnWidths) {
        List<XWPFTableRow> rows = table.getRows();
        for (int j = 1; j < rows.size(); j++) {
            XWPFTableRow row = rows.get(j);
            for (int i = 0; i < row.getTableCells().size(); i++) {
                row.getCell(i).getCTTc().addNewTcPr().addNewTcW().setW(BigInteger.valueOf(columnWidths[i]));
                if (j == 1) {
                    row.getCell(i).setColor("E7E6E6");
                }
            }
        }
    }

    private static void setTableGridCol2(XWPFTable table, int[] columnWidths) {
        List<XWPFTableRow> rows = table.getRows();
        for (int j = 0; j < rows.size(); j++) {
            XWPFTableRow row = rows.get(j);
            for (int i = 0; i < row.getTableCells().size(); i++) {
                row.getCell(i).getCTTc().addNewTcPr().addNewTcW().setW(BigInteger.valueOf(columnWidths[i]));
                //row.getCell(i).setWidth(String.valueOf(columnWidths[i]));
                if (j == 0) {
                    row.getCell(i).setColor("E7E6E6");
                }
            }
        }
    }

    public static void mergeCellsByCol(XWPFTable table, XWPFTableRow row, int fromcell, int endcell) {
        //2013及之前的老版本自己合并自己会出乱版，这里防止自己合并自己
        if(fromcell == endcell){
            return;
        }
        for (int cellindex = fromcell; cellindex <= endcell; cellindex++) {
            XWPFTableCell cell = row.getCell(cellindex);
            CTTc ctTc = cell.getCTTc();
            CTTcPr tcPr = ctTc.isSetTcPr() ? ctTc.getTcPr() : ctTc.addNewTcPr();
            CTHMerge hMerge = tcPr.isSetHMerge() ? tcPr.getHMerge() : tcPr.addNewHMerge();
            if (cellindex == fromcell) {
                //这个判断是为了处理 2 3 合并  之后又 3 4 合并，如果不做判断最终合并的只有3 4
                if(!STMerge.CONTINUE.equals(hMerge.getVal())){
                    hMerge.setVal(STMerge.RESTART);
                }
            } else {
                hMerge.setVal(STMerge.CONTINUE);
            }
        }
    }
}
